﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Transactions;
using Inventione.Framework.BusinessLogic;
using Inventione.Framework.Common;
using Inventione.Framework.DataAccess;
using Inventione.Framework.DataAccess.Exceptions;
using Inventione.Outsourcing.DataAccess;
using Inventione.Outsourcing.DataLinq;
using Inventione.Framework.BusinessLogic.Exceptions;

namespace Inventione.Outsourcing.BusinessLogic
{
    /// <summary>
    /// Classe de negócio de Medição
    /// </summary>
    public class BLMedicao : BLBase
    {
        // Representa o objeto de acesso a dados
        private DAMedicao da = null;

        /// <summary>
        /// Construtor padrão
        /// </summary>
        public BLMedicao()
            : base("DEFAULT")
        {
            da = new DAMedicao("DEFAULT");
        }

        /// <summary>
        /// Construtor utilizando o cliente
        /// </summary>
        /// <param name="tenantId">Identificador do cliente</param>
        public BLMedicao(string tenantId)
            :base (tenantId)
        {
            da = new DAMedicao(tenantId);
        }

        /// <summary>
        /// Regista uma medição para um periodo de um contrato
        /// </summary>
        /// <param name="entity">Entidade medição</param>
        /// <returns>Código da medição registrada</returns>
        public int realizarMedicao(MEDICAO entity)
        {
            int retorno = 0;

            try
            {
                // Não é possível realizar medição futura
                if (entity.MEDI_DT_FINAL > DateTime.Today)
                {
                    throw new BLException("Não é possível realizar medição de período futuro.");
                }

                // Valida se existe uma medição para o período
                if (this.existeMedicaoNoPeriodo(entity.CONT_CD_CONTRATO, entity.MEDI_DT_INICIAL, entity.MEDI_DT_FINAL, "C", false))
                {
                    throw new BLException("Já existe medição para o período informado.");
                }

                // Valia se o contrato está ou estava em vigência no período da medição
                BLContrato blContrato = new BLContrato(this.TenantId);
                if (!blContrato.validarVigencia(entity.CONT_CD_CONTRATO, entity.MEDI_DT_INICIAL, entity.MEDI_DT_FINAL))
                {
                    throw new BLException("Contrato não está/estava vigente no período informado.");
                }

                // Calcula os valores da medição
                TOMedicaoOcorrencias medicaoOcorrencias = this.calcularMedicao(entity);

                // Registra a medição e retorna o código
                retorno = this.da.realizarMedicao(medicaoOcorrencias);
            }
            catch (BLException blex)
            {
                throw blex;
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Verifica se existe uma medição de um contrato no período informado, sem considerar as canceladas
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="dataInicial">Data inicial do período</param>
        /// <param name="dataInicial">Data final do período</param>
        /// <param name="inStatus">Status da medição</param>
        /// <returns>Se há medição (true / false)</returns>
        public bool existeMedicaoNoPeriodo(int cdContrato, DateTime dataInicial, DateTime dataFinal, string inStatus, bool considerarStatus)
        {
            bool retorno = false;

            try
            {
                retorno = this.da.existeMedicaoNoPeriodo(cdContrato, dataInicial, dataFinal, inStatus, considerarStatus);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Verifica se existe medição de um contrato em uma data, considerando ou desconsiderando o status informado
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="dataBase">Data base</param>
        /// <param name="inStatus">Status da medição </param>
        /// <param name="considerarStatus">Indica se é para considerar o status informado ou os outros status existentes</param>
        /// <returns>Se há medição (true / false)</returns>
        public bool existeMedicaoNaData(int cdContrato, DateTime dataBase, string inStatus, bool considerarStatus)
        {
            bool retorno = false;

            try
            {
                retorno = this.da.existeMedicaoNaData(cdContrato, dataBase, inStatus, considerarStatus);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Verifica se existe medição de um contrato após uma data, considerando ou desconsiderando o status informado
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="dataBase">Data base</param>
        /// <param name="inStatus">Status da medição </param>
        /// <param name="considerarStatus">Indica se é para considerar o status informado ou os outros status existentes</param>
        /// <returns>Se há medição (true / false)</returns>
        public bool existeMedicaoAposData(int cdContrato, DateTime dataBase, string inStatus, bool considerarStatus)
        {
            bool retorno = false;

            try
            {
                retorno = this.da.existeMedicaoAposData(cdContrato, dataBase, inStatus, considerarStatus);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Lista autores de medições
        /// </summary>
        /// <param name="cdContrato">Contrato que as medições estão vinculadas</param>
        /// <returns>Lista de autores que realizaram medição</returns>
        public List<TOCombo> listarAutoresMedicaoContratoCombo(int cdContrato)
        {
            List<TOCombo> retorno = null;

            try
            {
                retorno = this.da.listarAutoresMedicaoContratoCombo(cdContrato);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Consulta as medições contratuais
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="inStatus">Indicador do status da medição</param>
        /// <param name="nmAutor">Nome do autor da medição</param>
        /// <param name="maximumRows">Quantidade máxima de linhas de retorno</param>
        /// <param name="startRowIndex">Linha inicial de retorno</param>
        /// <param name="sortExpression">Compo para ordenação</param>
        /// <returns>Lista de medições</returns>
        public List<MEDICAO> consultarMedicoes(int cdContrato, string inStatus, string nmAutor, int maximumRows, int startRowIndex, string sortExpression)
        {
            List<MEDICAO> retorno = null;

            try
            {
                retorno = this.da.consultarMedicoes(cdContrato, inStatus, nmAutor, maximumRows, startRowIndex, sortExpression);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Consulta as medições contratuais
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="inStatus">Indicador do status da medição</param>
        /// <param name="nmAutor">Nome do autor da medição</param>
        /// <returns>Quantidade de medições atingidas pela consulta</returns>
        public int consultarMedicoesCount(int cdContrato, string inStatus, string nmAutor)
        {
            int retorno = 0;

            try
            {
                retorno = da.consultarMedicoesCount(cdContrato, inStatus, nmAutor);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Obtem uma medição
        /// </summary>
        /// <param name="cdCidade">Código da medição</param>
        /// <returns>Entidade medição</returns>
        public MEDICAO obterMedicao(int cdMedicao)
        {
            MEDICAO retorno = null;

            try
            {
                retorno = da.obterMedicao(cdMedicao);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Obtem a medição 
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="dtBase">Data base para considerar um período</param>
        /// <returns>Lista de medições</returns>
        public List<MEDICAO> obterMedicoesNaData(int cdContrato, DateTime dtBase)
        {
            List<MEDICAO> retorno = null;

            try
            {
                retorno = da.obterMedicoesNaData(cdContrato, dtBase);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }

            return retorno;
        }

        /// <summary>
        /// Obtem o resumo da medição
        /// </summary>
        /// <param name="entity">Entidade medição</param>
        /// <returns>Resumo da medição</returns>
        public TOMedicaoOcorrencias calcularMedicao(MEDICAO entity)
        {
            // Variaveis para ao calculo
            decimal vlDiaCargo = 0;
            decimal vlOcupacao = 0;
            decimal vlSubTotal = 0;

            decimal vlOcorrencia = 0;
            decimal vlOcorrenciasNegativas = 0;
            decimal vlOcorrenciasPositivas = 0;
            decimal vlSubTotalOcorPositivas = 0;
            decimal vlSubTotalOcorNegativas = 0;

            decimal vlTotal = 0;

            // Objeto de negócio de ocupação
            BLOcupacao blOcupacao = new BLOcupacao(base.TenantId);

            /* 
             * Obtendo a lista de ocupações com base no contrato e período de medição.
             * Obs.: O período será com base no parâmetro informado na medição. 
             *       Para o cálculo será utilizado a quantidade de dias também informada na medição.
             */
            List<TOQuantidadeOcupacao> listOcupSumarizadas = blOcupacao.sumarizarOcupacoesMedicaoContratual(entity);

            // Lista de ocorrência da medição
            List<OCORRENCIA> ocorrencias = new List<OCORRENCIA>();

            // Iterando as 
            foreach (TOQuantidadeOcupacao item in listOcupSumarizadas)
            {
                // Calcula o valor de 1 dia para o cargo, com base na medicao
                vlDiaCargo = Util.calcularValorMedia(item.Ocupacao.CARGO.CARG_VL_REFERENCIA, entity.MEDI_QN_DIAS_BASE_CALCULO, 2);

                // Valor de uma ocupação do cargo
                vlOcupacao = vlDiaCargo * item.QuantidadeDias;

                // Reiniciando as variáveis
                vlOcorrencia = 0;
                vlOcorrenciasNegativas = 0;
                vlOcorrenciasPositivas = 0;

                // Iterando as ocorrências
                foreach (var ocor in item.Ocupacao.OCORRENCIAs)
                {
                    // Valor da ocorrência
                    vlOcorrencia = Math.Round((vlDiaCargo * ocor.OCOR_NR_PERCENTUAL_DIARIO) / 100, 2);

                    // Definindo sumarizando de acordo com a natureza
                    if (ocor.TIPO_OCORRENCIA.TIOC_IN_NATUREZA.Equals('C'))
                    {
                        vlOcorrenciasPositivas = vlOcorrenciasPositivas + vlOcorrencia;
                    }
                    else if (ocor.TIPO_OCORRENCIA.TIOC_IN_NATUREZA.Equals('D'))
                    {
                        vlOcorrenciasNegativas = vlOcorrenciasNegativas + vlOcorrencia;
                    }

                    ocorrencias.Add(ocor);
                }

                // Valores sub-totais 
                vlSubTotalOcorPositivas = vlSubTotalOcorPositivas + vlOcorrenciasPositivas;
                vlSubTotalOcorNegativas = vlSubTotalOcorNegativas + vlOcorrenciasNegativas;

                // Valor do sub-total
                vlSubTotal = vlSubTotal + vlOcupacao;
            }

            // Calculo do total
            vlTotal = vlSubTotal - vlSubTotalOcorNegativas + vlSubTotalOcorPositivas;

            // Dados do resumo
            entity.MEDI_VL_SUBTOTAL = vlSubTotal;
            entity.MEDI_VL_OCORRENCIAS_NEGATIVAS = vlSubTotalOcorNegativas;
            entity.MEDI_VL_OCORRENCIAS_POSITIVAS = vlSubTotalOcorPositivas;
            entity.MEDI_VL_TOTAL = vlTotal;

            return new TOMedicaoOcorrencias() { Medicao = entity, Ocorrencias = ocorrencias };
        }

        /// <summary>
        /// Aprova uma medição
        /// </summary>
        /// <param name="cdMedicao">Código da medição</param>
        /// <param name="nmAutor">Nome do autor da aprovação</param>
        public void aprovarMedicao(int cdMedicao, string nmAutor)
        {
            try
            {
                da.atualizarStatusMedicao(cdMedicao, nmAutor, "A", 0);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }
        }

        /// <summary>
        /// Cancelar uma medição
        /// </summary>
        /// <param name="cdMedicao">Código da medição</param>
        /// <param name="nmAutor">Nome do autor do cancelamento</param>
        /// <param name="cdMotivoCancelamento">Código do motivo do cancelamento</param>
        public void cancelarMedicao(int cdMedicao, string nmAutor, int cdMotivoCancelamento)
        {
            try
            {
                da.atualizarStatusMedicao(cdMedicao, nmAutor, "C", cdMotivoCancelamento);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }
        }

        /// <summary>
        /// Cancelar as medições de uma data
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="dataBase">Data base</param>
        /// <param name="cdMotivoCancelamento">Código do motivo de cancelamento</param>
        /// <param name="nmAutor">Nome do autor do cancelamento</param>
        public void cancelarMedicoesNaData(int cdContrato, DateTime dataBase, int cdMotivoCancelamento, string nmAutor)
        {
            try
            {
                da.cancelarMedicoesNaData(cdContrato, dataBase, cdMotivoCancelamento, nmAutor);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }
        }

        /// <summary>
        /// Cancelar as medições de um período
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="dataInicial">Data inicial</param>
        /// <param name="dataFinal">Data final</param>
        /// <param name="cdMotivoCancelamento">Código do motivo de cancelamento</param>
        /// <param name="nmAutor">Nome do autor do cancelamento</param>
        public void cancelarMedicoesNoPeriodo(int cdContrato, DateTime dataInicial, DateTime dataFinal, int cdMotivoCancelamento, string nmAutor)
        {
            try
            {
                da.cancelarMedicoesNoPeriodo(cdContrato, dataInicial, dataFinal, cdMotivoCancelamento, nmAutor);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }
        }

        /// <summary>
        /// Cancelar as medições após uma data
        /// </summary>
        /// <param name="cdContrato">Código do contrato</param>
        /// <param name="dataBase">Data base</param>
        /// <param name="cdMotivoCancelamento">Código do motivo de cancelamento</param>
        /// <param name="nmAutor">Nome do autor do cancelamento</param>
        public void cancelarMedicoesAPartirDaData(int cdContrato, DateTime dataBase, int cdMotivoCancelamento, string nmAutor)
        {
            try
            {
                da.cancelarMedicoesAPartirDaData(cdContrato, dataBase, cdMotivoCancelamento, nmAutor);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }
        }

        /// <summary>
        /// Submete uma medição para aprovação
        /// </summary>
        /// <param name="cdMedicao">Código da medição</param>
        /// <param name="nmAutor">Nome do autor que está submetendo a aprovação</param>
        public void submeterMedicaoParaAprovacao(int cdMedicao, string nmAutor)
        {
            try
            {
                da.atualizarStatusMedicao(cdMedicao, nmAutor, "S", 0);
            }
            catch (Exception ex)
            {
                throw new BLException(ex, true);
            }
        }
    }
}
